#!/usr/bin/env python3
# -*- coding:utf-8 -*-

# 定义函数:定义一个函数要使用def语句，依次写出函数名、括号、括号中的参数和冒号:，然后，在缩进块中编写函数体，函数的返回值用return语句返回


def my_abs(x):
    if x < 0:
        x = -x
    return x


print(my_abs(-1))


# 空函数
# 如果想定义一个什么事也不做的空函数，可以用pass语句：
def nop():
    pass


# pass语句什么都不做，那有什么用？实际上pass可以用来作为占位符，
# 比如现在还没想好怎么写函数的代码，就可以先放一个pass，让代码能运行起来
# pass还可以用在其他语句里


# isinstance :类型检查可以用内置函数isinstance()实现
"""
def my_abs(x):
    if not isinstance(x, (int, float)):
        raise TypeError('bad operand type')  # 抛出异常
    if x < 0:
        x = -x

    return x

print(my_abs("x"))
"""

# 返回多个值,
# 返回值是一个tuple！但是，在语法上，返回一个tuple可以省略括号，而多个变量可以同时接收一个tuple，
# 按位置赋给对应的值，所以，Python的函数返回多值其实就是返回一个tuple，但写起来更方便

"""
def move(x, y, step, angle=0):
    nx = x + step * math.cos(angle)
    ny = y - step * math.sin(angle)
    return nx, ny
"""

"""
定义函数时，需要确定函数名和参数个数；

如果有必要，可以先对参数的数据类型做检查；

函数体内部可以用return随时返回函数结果；

函数执行完毕也没有return语句时，自动return None。

函数可以同时返回多个值，但其实就是一个tuple。

"""


# 可变参数
def calculate(numners):
    sum = 0
    for num in numners:
        sum += pow(num, 2)
    print(sum)
    return sum


# 但是调用的时候，需要先组装出一个list或tuple：
calculate([1, 2, 3])
calculate([1, 3, 5, 7])


# 利用可变参数，调用函数的方式可以简化成这样
# *nums表示把nums这个list的所有元素作为可变参数传进去

def calculate(*numbers):
    sum = 0
    for num in numbers:
        sum += pow(num, 2)
    print(sum)
    return sum


calculate(1, 2, 3)
calculate()  # 不传任何参数

# 关键字参数
"""
可变参数允许你传入0个或任意个参数，这些可变参数在函数调用时自动组装为一个tuple。
而关键字参数允许你传入0个或任意个含参数名的参数，这些关键字参数在函数内部自动组装为一个dict
"""


# 函数person除了必选参数name和age外，还接受关键字参数kw
def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)


person("james", "29")  # name: james age: 29 other: {}

other = {"city": "beijing"}
person("james", "29", **other)  # name: james age: 29 other: {'city': 'beijing'}

"""
**extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数，kw将获得一个dict，
注意kw获得的dict是extra的一份拷贝，对kw的改动不会影响到函数外的extra。

注意与 java 的区别:
java 中 List,Map,方法内修改了list/map,会影响外部的 list/map

"""


# 命名关键字参数:对于关键字参数，函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些，就需要在函数内部通过kw检查。

def old_person(name, age, **kw):
    if 'city' in kw:
        # 有 city 参数
        kw['city'] = 'houston'
    if 'job' in kw:
        pass

    print('name:', name, 'age:', age, 'other:', kw)


print(other)  # {'city': 'beijing'}, function old_person()中修改的 kw, 是 other 的备份,修改备份不会影响到外层的 extra

old_person("james", "29", **other)  # name: james age: 29 other: {'city': 'houston'}


# 限制关键字参数的名字: *
# 关键字参数**kw不同，命名关键字参数需要一个特殊分隔符*，*后面的参数被视为命名关键字参数。

# e.g:只接受city和job作为关键字参数,即参数 key 必须为 city 和 job

def old_person2(name, age, *, city, job):
    print(name, age, city, job)


old_person2('Jack', 24, city='Beijing', job='Engineer')  # Jack 24 Beijing Engineer

"""
old_person2('Jack', 24, city1='Beijing', job1='Engineer') 
TypeError: old_person2() got an unexpected keyword argument 'city1'
"""


# 函数定义中已经有了一个可变参数，后面跟着的命名关键字参数就不再需要一个特殊分隔符*了
def person2(name, age, *args, city, job):
    print(name, age, args, city, job)


person2("james", "29", city="houston", job="mvp")  # james 29 () houston mvp

person2("james", "29", other, city="houston", job="mvp")  # james 29 ({'city': 'beijing'},) houston mvp

person2("james", "29", *other, city="houston", job="mvp")  # 错误用法!

"""
Python的函数具有非常灵活的参数形态，既可以实现简单的调用，又可以传入非常复杂的参数。

默认参数一定要用不可变对象，如果是可变对象，程序运行时会有逻辑错误！

要注意定义可变参数和关键字参数的语法：

*args是可变参数，args接收的是一个tuple；

**kw是关键字参数，kw接收的是一个dict。

以及调用函数时如何传入可变参数和关键字参数的语法：

可变参数既可以直接传入：func(1, 2, 3)，又可以先组装list或tuple，再通过*args传入：func(*(1, 2, 3))；

关键字参数既可以直接传入：func(a=1, b=2)，又可以先组装dict，再通过**kw传入：func(**{'a': 1, 'b': 2})。

使用*args和**kw是Python的习惯写法，当然也可以用其他参数名，但最好使用习惯用法。

命名的关键字参数是为了限制调用者可以传入的参数名，同时可以提供默认值。

定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*，否则定义的将是位置参数。
"""
